package com.apobates.forum.utils.image;

import java.awt.Color;
import java.awt.Font;
import java.awt.FontFormatException;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.GraphicsEnvironment;
import java.awt.Rectangle;
import java.awt.font.FontRenderContext;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.apobates.forum.utils.Commons;

/**
 * 文本类型的遮盖元素
 *
 * @author xiaofanku
 * @since 20200329
 */
public final class OverlayTextElement implements OverlayElement {
    private final String content;
    private final TextElementConfig config;
    private final static Logger logger = LoggerFactory.getLogger(OverlayTextElement.class);
    
    public OverlayTextElement(String content) {
        super();
        this.content = content;
        this.config = TextElementConfig.getDefault();
    }
    
    public OverlayTextElement(String content, TextElementConfig config) {
        this.content = content;
        this.config = config;
    }
    
    @Override
    public BufferedImage getData() throws IOException, OverlayBoxException {
        Font font = getFont();
        if (null == font) {
            throw new OverlayBoxException("文字缺少必要的字体实例");
        }
        //
        int fontWidth = 0, fontHeight = 0;
        float clientY = 0.0f, clientX = 0.0f;
        if (config.getRectWidth() > 0 && config.getRectHeight() > 0) {
            fontWidth = config.getRectWidth();
            fontHeight = config.getRectHeight();
        } else {
            // 计算面积
            // https://stackoverflow.com/questions/1524855/how-to-calculate-the-fonts-width
            // https://stackoverflow.com/questions/1399536/java-fonts-and-pixels
            Rectangle rt = font.getStringBounds(content, new FontRenderContext(font.getTransform(), true, true)).getBounds();
            fontWidth = Double.valueOf(rt.getWidth()).intValue();
            fontHeight = Double.valueOf(rt.getHeight()).intValue();
            if (fontWidth < config.getSize() || fontHeight < font.getSize() / 10) {
                throw new OverlayBoxException("文字副面面积计算失败");
            }
            fontWidth += 5;
            fontHeight -= 20;
            clientY = Math.round(config.getSize() / 1.41421) + 5; // 1.41421(毕达哥拉斯常数)
        }
        //
        BufferedImage resized = new BufferedImage(fontWidth, fontHeight, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2d = (Graphics2D) resized.getGraphics();
        g2d.setColor(parseStringColor(config.getColor()));
        g2d.setFont(font);
        //
        if (clientY == 0.0f) {
            float[] coordinate = calcFixClientCoordinate(g2d, config.getRectWidth(), config.getRectHeight());
            clientX = coordinate[0];
            clientY = coordinate[1];
        }
        
        g2d.drawString(content, clientX, clientY);
        // 
        g2d.dispose();
        return resized;
    }
    
    private Font getFont() throws IOException {
        try {
            if (null != config.getOutFontFile() && config.getOutFontFile().exists()) {
                GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
                Font tmp = Font.createFont(Font.TRUETYPE_FONT, config.getOutFontFile());
                ge.registerFont(tmp);
            }
        } catch (FontFormatException e) {
            if (logger.isDebugEnabled()) {
                logger.debug("[OTE]挂接系统外部字体失败", e);
            }
        }
        return new Font(config.getFamily(), (config.isBold() ? Font.BOLD : Font.PLAIN), config.getSize());
    }
    
    private float[] calcFixClientCoordinate(Graphics2D graphics, int imageWidth, int imageHeight) {
        FontMetrics fontMetrics = graphics.getFontMetrics();
        Rectangle2D rect = fontMetrics.getStringBounds(content, graphics);
        long rh = Math.round(rect.getHeight());
        
        if (imageWidth < rect.getWidth()) {
            throw new OverlayBoxException("文字副面宽度过窄");
        }
        if (imageHeight < rect.getHeight()) {
            throw new OverlayBoxException("文字副面高度过窄");
        }
        double x = 0, y = imageHeight;
        if (imageWidth > rect.getWidth()) {
            x = (imageWidth - rect.getWidth()) / 2;
        }
        
        if (imageHeight > rh) {
            int halfH = imageHeight / 2;
            y = halfH - rh / 2 + halfH;
        }
        return new float[]{Double.valueOf(x).floatValue(), Double.valueOf(y).floatValue()};
    }
    // https://stackoverflow.com/questions/4129666/how-to-convert-hex-to-rgb-using-java
    private Color parseStringColor(String colorStr) {
        try {
            return new Color(
                    Integer.valueOf(colorStr.substring(1, 3), 16),
                    Integer.valueOf(colorStr.substring(3, 5), 16),
                    Integer.valueOf(colorStr.substring(5, 7), 16));
        } catch (NullPointerException | StringIndexOutOfBoundsException e) {
            throw new OverlayBoxException("文字颜色解析失败,请使用十六进制的全值,不可简写");
        }
    }
    
    @Override
    public boolean isUsable() {
        return Commons.isNotBlank(content);
    }
    
    /**
     * 文字配置
     *
     * @author xiaofanku
     * @since 20200331
     */
    public static class TextElementConfig {
        private int size = 50;
        private String family = "SansSerif";
        private String color = "#FF0000";
        private boolean bold = false;
        private int rectWidth = 0;
        private int rectHeight = 0;
        private File outFontFile = null;
        
        private TextElementConfig() {
        }
        
        /**
         * 默认的文字配置,字体:SansSerif,字号:50,粗体:否,颜色:#FF0000
         *
         * @return
         */
        public static TextElementConfig getDefault() {
            return new TextElementConfig();
        }
        
        /**
         * 文字的大小,设置过大可能在二维码时导致无法解析;默认为50
         *
         * @param fontSize
         * @return
         */
        public TextElementConfig fontSize(int fontSize) {
            if (fontSize > 0) {
                this.size = fontSize;
            }
            return this;
        }
        
        /**
         * 文字是否粗体,默认为false
         *
         * @param boldFont true粗体
         * @return
         */
        public TextElementConfig isBold(boolean boldFont) {
            this.bold = boldFont;
            return this;
        }
        
        /**
         * 文字的字体,请使用系统平台支持的字体;默认为SansSerif
         *
         * @param fontFamily
         * @return
         */
        public TextElementConfig fontFamily(String fontFamily) {
            if (Commons.isNotBlank(fontFamily)) {
                this.family = fontFamily;
            }
            return this;
        }
        
        /**
         * 文字的颜色,只支持十六进制的值,例:#FFFFFF;默认为#FF0000
         *
         * @param fontColor
         * @return
         */
        public TextElementConfig fontColor(String fontColor) {
            if (Commons.isNotBlank(fontColor)) {
                this.color = fontColor;
            }
            return this;
        }
        
        /**
         * 可选项,挂接系统外部字体.<br/>
         * 注意先挂接再设置fontFamily,不可反向操作<br/>
         *
         * @param fontFile 字体文件
         * @return
         */
        public TextElementConfig outFont(File fontFile) {
            this.outFontFile = fontFile;
            return this;
        }
        
        /**
         * 可选项:设置文字区域的宽和高<br/>
         * 不设程序会计算,系统内置字体都可以计算出结果.<br/>
         * 挂接系统外部字体都需要调用此方法<br/>
         *
         * @param width 文字区域的宽
         * @param height 文字区域的高
         * @return
         */
        public TextElementConfig rectangle(int width, int height) {
            if (width < 10 || height < 10) {
                return this;
            }
            this.rectWidth = width;
            this.rectHeight = height;
            return this;
        }
        
        public int getSize() {
            return size;
        }
        
        public String getFamily() {
            return family;
        }
        
        public boolean isBold() {
            return bold;
        }
        
        public String getColor() {
            return color;
        }
        
        public int getRectWidth() {
            return rectWidth;
        }
        
        public int getRectHeight() {
            return rectHeight;
        }
        
        public File getOutFontFile() {
            return outFontFile;
        }
    }
}